// *********************************************************
//      Copyright (c) Kosta Gaitanis
//      All rights reserved
//
//      k.gaitanis@gmail.com
//
// *********************************************************

#include "Item.h"

Item::Item(const QString &name) :
    m_dirty(true),
    m_name(name),
    m_parent(0),
    m_displayListId(-1),
    m_texture(0),
    m_shareMaterialWithParent(false),
    m_frame(0),
    m_visible(true)
{
    // set transform to identity matrix
    setTransform(Matrix::Identity);
}

Item::~Item()
{
    //START//
    // delete the display list from the GPU memory
    glDeleteLists(m_displayListId, 1);
    //END//

    // delete all the faces of this item
    qDeleteAll(m_faces);

    // delete all children
    qDeleteAll(m_children);
}

void Item::initializeGL()
{
    //START//
    // generate display lists for this item and store it in m_displayListId
    m_displayListId = glGenLists(1);
    //END//

    // initialize the children
    foreach (Item* child, m_children) {
        child->initializeGL();
    }
}

void Item::paint(Camera *camera)
{
    Q_UNUSED(camera);
    // if item is not visible or does not have any faces, do nothing
    if (!isVisible() || count() == 0) {
        return;
    }

    if (isDirty()) {
        // this item has changed since the last paint. We need to refresh the display list.
        //START//
        // regenerate the display list and paint at the same time.
        glNewList(m_displayListId, GL_COMPILE_AND_EXECUTE);
        //END//

        if (getTexture()) {
            // if a texture is present, bind it that way it will be rendered
            getTexture()->paintGL();
        } else {
            // no texture, avoid painting the previously loaded texture
            glBindTexture(GL_TEXTURE_2D, 0);
        }

        // load the material parameters
        getMaterial()->paint();

        // paint each face
        foreach(Face* face, m_faces) {
            face->paint();
        }

        //START//
        // end the display list
        glEndList();
        //END//

        // the display list does not need to be refreshed anymore.
        m_dirty = false;

    } else {
        // there have been no modifications since the last paint
        //START//
        // simply draw the display list.
        glCallList(m_displayListId);
        //END//
    }

}


const QString &Item::getName() const
{
    return m_name;
}

void Item::setName(const QString &name)
{
    m_name = name;
}

void Item::addChild(Item *item)
{
    if (hasChild(item, true)) {
        qDebug() << "Item" << item->getName() << "is already a child of" << getName();
        return;
    }
    setDirty();
    m_children.append(item);
    item->m_parent = this;
}

void Item::deleteChild(const QString &name)
{
    foreach (Item *child, m_children) {
        if (child->getName() == name) {
            setDirty();
            m_children.removeOne(child);
            delete child;
            return;
        }
    }
}

void Item::removeChild(Item *child)
{
    if (m_children.contains(child)) {
        m_children.removeOne(child);
        child->m_parent = 0;
        setDirty();
    } else {
        qDebug() << "Item" << child->getName() << "is not a child of" << getName();
    }
}

bool Item::hasChild(Item *child, bool recursive) const
{
    if (getChildren(recursive).contains(child)) {
        return true;
    }

    return false;
}

bool Item::hasChild(const QString &name, bool recursive) const
{
    if (getChildrenNames().contains(name)) {
        return true;
    }
    if (recursive) {
        foreach (Item* item, m_children) {
            if (item->hasChild(name, true)) {
                return true;
            }
        }
    }
    return false;
}

QStringList Item::getChildrenNames() const
{
    QStringList names;

    foreach (Item *child, m_children) {
        names.append(child->getName());
    }

    return names;
}

ItemList Item::getChildren(bool recursive) const
{
    if (!recursive) {
        return m_children;
    }

    ItemList children;
    foreach (Item* child, m_children) {
        children.append(child);
        children.append(child->getChildren(true));
    }

    return children;
}

Item *Item::getChild(const QString &name) const
{
    foreach (Item *child, m_children) {
        if (child->getName() == name) {
            return child;
        }
    }

    return 0;
}

const GLdouble *Item::getGLTransform() const
{
    return m_glTransform;
}

Matrix Item::getTransform() const
{
    return m_transform;
}

void Item::setTransform(const GLdouble *glTransform)
{
    for (int i = 0; i < 16; i++) {
        m_glTransform[i] = glTransform[i];
    }
    m_transform = Matrix::fromGLArray(glTransform);
}

void Item::setTransform(const Matrix &transform)
{
    m_transform = transform;
    transform.toGLArray(m_glTransform);
}

void Item::addFace(const Face &face)
{
    setDirty();
    m_faces.append(new Face(face));
}

int Item::count() const
{
    return m_faces.count();
}

bool Item::isEmpty() const
{
    return m_faces.isEmpty();
}

bool Item::isDirty() const
{
    return m_dirty;
}

void Item::setTexture(const Texture *texture)
{
    m_texture = texture;
    setDirty();
}

const Texture *Item::getTexture() const
{
    return m_texture;
}

void Item::setMaterial(const Material &material)
{
    m_material = material;
    setDirty();
}

Material *Item::getMaterial()
{
    if (m_shareMaterialWithParent && m_parent) {
        return m_parent->getMaterial();
    }
    return &m_material;
}

void Item::setDirty()
{
    m_dirty = true;

    foreach (Item* child, getChildren()) {
        if (child->m_shareMaterialWithParent) {
            child->setDirty();
        }
    }
}

void Item::setFrame(double frame)
{
    m_frame = frame;
    onFrameChanged(frame);
    foreach (Item* child, m_children) {
        child->setFrame(frame);
    }
}

double Item::getFrame() const
{
    return m_frame;
}

void Item::setVisible(bool visible)
{
    m_visible = visible;
}

bool Item::isVisible() const
{
    return m_visible;
}

Vector Item::getCurrentPosition() const
{
    return getGlobalTransform() * Vector::ZERO;
}

Matrix Item::getGlobalTransform() const
{
    if (m_parent) {
        return m_parent->getGlobalTransform() * getTransform();
    }
    return getTransform();
}

void Item::setShareMaterialWithParent(bool share)
{
    m_shareMaterialWithParent = share;
}

void Item::onFrameChanged(double frame)
{
    Q_UNUSED(frame)
}

